"""
通信簿类。
至少包含联系人数目，联系人列表（包含联系人加入时间，更新时间）等属性，
以及加入新联系人（注意重复的电话或邮件不应加入），
    更新联系人（如果不存在应该提醒），
    搜索联系人（可按姓名，电话，邮件来分别搜索），
    联系人排序输出（可按姓名，电话，邮件来进行升降序排列）等行为。
"""

from datetime import datetime as dt

from pypinyin import lazy_pinyin

from Contact import Contact, Head
from UserException import *


class AddressBook:
    """
    通信簿类。
    包含联系人数目，联系人列表（包含联系人信息、加入时间，更新时间），最近更新时间等属性，
    以及加入新联系人（注意重复的电话或邮件不应加入），
        更新联系人（如果不存在应该提醒），
        搜索联系人（可按姓名，电话，邮件来分别搜索），
        联系人排序输出（可按姓名，电话，邮件来进行升降序排列）
        查看联系人是否存在，
        实现了通讯簿的导入和导出等方法。
    """
    RS = Contact.atom_attr

    def __init__(self, *contacts):
        """
        Instance Initialize.
        """

        self.__records = []
        for contact in contacts:
            self._add(contact)
        self._last_update = self.__records[-1][-1] if self.__records else ""

    def get_records_num(self):
        """
        获得总记录数
        :return: int, 记录数
        """
        return len(self.__records)

    @property
    def records(self):
        """
        返回所有记录
        """
        return self.__records

    @property
    def last_update(self):
        """
        返回最后更新时间
        """
        return self._last_update

    def has_record(self, contact):
        """
        查看是否有重复。
        """
        t = self.search("tel", contact.get_tel())
        e = self.search("email", contact.get_email())

        if t:
            return 1
        elif e:
            return 2
        else:
            return 0

    def _add(self, contact, *t, load=False):
        """
        添加联系人
        :param contact: 联系人实例
        :param t: 时间信息，若load为真，则记录的时间依据此参数，第一个为创建时间，第二个为修改时间
        :param load: 是否为文件导入
        """
        # 查看是否有冲突
        i = self.has_record(contact)
        if i:
            raise AddError(contact, i)
        else:
            # 是否为导入模式
            if load:
                # 若是，时间信息由文件提供
                self.__records.append([contact, t[0], t[1]])
            else:
                # 否则，时间信息为当下
                t = dt.now()
                self.__records.append([contact, t, t])
            # 更新最后更更新时间
            self._last_update = dt.now()

    def add(self, name, tel, email, head):
        """
        根据输入的属性创建联系人实例，添加联系人

        :param name: 姓名
        :param tel: 电话
        :param email: 电子邮箱
        :param head: 头像实例
        """

        try:
            self._add(Contact(name, tel, email, head))
        except AddError as a:
            a.print_error()

    def update(self, attr, old, new):
        """
        更新属性
            若更新头像，则attr输入"name"，old输入姓名，new输入新的头像实例

        :param attr: 属性名
        :param old: 旧属性值
        :param new: 新属性值
        """
        try:
            # 若属性名允许更新
            if attr in Contact.all_attr[1:]:

                # 检查是否存在
                ti = self.search(attr, old, index=True)
                if not ti:
                    raise UpdateError(1, attr, old)
                else:
                    # 若更新头像
                    if isinstance(new, Head):
                        self.__records[ti[0]][0].head = new
                        t = dt.now()
                        self.__records[ti[0]][2] = t
                        self._last_update = t
                    # 若更新电话或邮箱
                    elif isinstance(new, str) and new != "":
                        # 检查新属性值是否已存在
                        if self.search(attr, new):
                            raise UpdateError(2, attr, old)
                        else:
                            if attr == self.RS[1]:
                                self.__records[ti[0]][0].set_tel(new)
                            else:
                                self.__records[ti[0]][0].set_email(new)
                        # 更新记录修改时间，以及通讯簿最终修改时间
                        t = dt.now()
                        self.__records[ti[0]][2] = t
                        self._last_update = t
                    else:
                        raise UpdateError(3, attr, old)
        except UpdateError as u:
            u.print_error()

    def search(self, attr, value, index=False):
        """
        单个条件的联系人查询。

        :param attr: 查询条件的属性名
        :param value: 查询条件的值
        :param index: 是否返回记录序号
        :return: index为真返回记录序号，若否则返回记录本身
        """
        try:
            if attr in self.RS:
                result = []
                for j in range(len(self.__records)):
                    if self.__records[j][0].get_attr(attr) == value:
                        if index:
                            result.append(j)
                        else:
                            result.append(self.__records[j][0])
                return result
            else:
                raise KeywordError(1, attr)
        except KeywordError as k:
            k.print_error()

    def show(self, rule="default", reverse=False):
        """
        联系人排序输出。

        :param rule: 输出排序；若输入"pinyin"，按姓名的汉语拼音排序
        :param reverse: 是否逆序
        """
        try:
            contacts = [recording[0] for recording in self.__records]
            if rule == "default":
                sorted_contacts = contacts
            else:
                if rule in self.RS:
                    key_func = lambda x: x.get_attr(rule)
                elif rule == "pinyin":
                    key_func = lambda x: "".join(lazy_pinyin(x.get_name()))
                else:
                    raise KeywordError(2, rule)
                sorted_contacts = sorted(contacts, key=key_func, reverse=reverse)

            for contact in sorted_contacts:
                print(contact)
        except KeywordError as k:
            k.print_error()

    def save(self, dir_path="./"):
        """
        导出通讯簿，文件名为导出时间，文件后缀为".yjab"

        :param dir_path: 文件夹路径
        :return: 导出的文件路径
        """
        t = dt.now()
        ab_path = dir_path + dt.strftime(t, "%Y%m%d%H%M%S") + ".yjab"
        with open(ab_path, "w", encoding="utf8") as f:
            f.write(str(t.timestamp()) + "\n")
            for recording in self.__records:
                f.write("\n" + recording[0].output())
                f.write("\nCreateTime: " + str(recording[1].timestamp()))
                f.write("\nUpdateTime: " + str(recording[2].timestamp()) + "\n")
        print("Data saved at {}\n".format(t))

        return ab_path

    def load(self, path):
        """
        导入通讯簿

        :param path: 文件路径
        """

        # 检查文件后缀
        if path[-5:] == ".yjab":

            # 预处理
            with open(path, "r", encoding="utf8") as f:
                s = f.read()
            record_list = s.split("\n\n")[1:]

            # 分条导入
            for i, record in enumerate(record_list, start=1):
                params = record.split("\n")
                name = params[0][6:]
                tel = params[1][5:]
                email = params[2][7:]
                head_info = params[3][6:].split("_")
                head = Head(head_info[0], int(head_info[1]), int(head_info[2]))
                c = Contact(name, tel, email, head)
                t1 = dt.fromtimestamp(float(params[4].lstrip("CreateTime: ")))
                t2 = dt.fromtimestamp(float(params[5].lstrip("UpdateTime: ")))

                # 尝试导入
                try:
                    self._add(c, t1, t2, load=True)
                except AddError as a:
                    a.print_error()
                    print(f"Failed NO. : {i}")  # 导入失败打印失败的序号
        else:
            print("The file to load must a '.yjab' file.")
